"""
安全服务
"""
import json
from datetime import datetime, timedelta
from typing import List, Optional, Dict, Any
from fastapi import Request
from loguru import logger

from app.utils.redis_client import redis_client
from app.schemas.auth import SecurityAuditLog


class SecurityService:
    """安全服务类"""
    
    # Redis键前缀
    AUDIT_LOG_PREFIX = "audit_log"
    LOGIN_ATTEMPTS_PREFIX = "login_attempts"
    BLOCKED_IPS_PREFIX = "blocked_ips"
    
    # 安全配置
    MAX_LOGIN_ATTEMPTS = 5
    LOCKOUT_DURATION = 900  # 15分钟
    AUDIT_LOG_RETENTION = 86400 * 30  # 30天
    
    @classmethod
    async def log_security_event(
        cls,
        action: str,
        user_id: Optional[int] = None,
        resource: Optional[str] = None,
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None,
        success: bool = True,
        details: Optional[Dict[str, Any]] = None,
        request: Optional[Request] = None
    ) -> bool:
        """记录安全事件"""
        try:
            # 从请求中提取信息
            if request:
                if not ip_address:
                    ip_address = request.client.host if request.client else None
                if not user_agent:
                    user_agent = request.headers.get("user-agent")
            
            # 创建审计日志
            audit_log = SecurityAuditLog(
                user_id=user_id,
                action=action,
                resource=resource,
                ip_address=ip_address,
                user_agent=user_agent,
                success=success,
                details=details or {},
                timestamp=datetime.now()
            )
            
            # 存储到Redis
            log_key = f"{cls.AUDIT_LOG_PREFIX}:{datetime.now().strftime('%Y%m%d')}:{audit_log.timestamp.timestamp()}"
            await redis_client.set(
                log_key,
                audit_log.dict(),
                expire=cls.AUDIT_LOG_RETENTION
            )
            
            # 如果是登录失败，记录失败尝试
            if action == "login" and not success and ip_address:
                await cls._record_login_attempt(ip_address)
            
            logger.info(f"安全事件记录: {action} - {success}")
            return True
            
        except Exception as e:
            logger.error(f"记录安全事件失败: {e}")
            return False
    
    @classmethod
    async def _record_login_attempt(cls, ip_address: str) -> bool:
        """记录登录尝试"""
        try:
            attempts_key = f"{cls.LOGIN_ATTEMPTS_PREFIX}:{ip_address}"
            
            # 获取当前尝试次数
            attempts = await redis_client.get(attempts_key) or 0
            attempts = int(attempts) + 1
            
            # 设置过期时间为1小时
            await redis_client.set(attempts_key, attempts, expire=3600)
            
            # 如果超过最大尝试次数，封锁IP
            if attempts >= cls.MAX_LOGIN_ATTEMPTS:
                await cls._block_ip(ip_address)
                logger.warning(f"IP地址被封锁: {ip_address} (尝试次数: {attempts})")
            
            return True
            
        except Exception as e:
            logger.error(f"记录登录尝试失败: {e}")
            return False
    
    @classmethod
    async def _block_ip(cls, ip_address: str) -> bool:
        """封锁IP地址"""
        try:
            blocked_key = f"{cls.BLOCKED_IPS_PREFIX}:{ip_address}"
            block_info = {
                "blocked_at": datetime.now().isoformat(),
                "reason": "Too many failed login attempts",
                "expires_at": (datetime.now() + timedelta(seconds=cls.LOCKOUT_DURATION)).isoformat()
            }
            
            await redis_client.set(blocked_key, block_info, expire=cls.LOCKOUT_DURATION)
            return True
            
        except Exception as e:
            logger.error(f"封锁IP失败: {e}")
            return False
    
    @classmethod
    async def is_ip_blocked(cls, ip_address: str) -> bool:
        """检查IP是否被封锁"""
        try:
            blocked_key = f"{cls.BLOCKED_IPS_PREFIX}:{ip_address}"
            block_info = await redis_client.get(blocked_key)
            return block_info is not None
            
        except Exception as e:
            logger.error(f"检查IP封锁状态失败: {e}")
            return False
    
    @classmethod
    async def unblock_ip(cls, ip_address: str) -> bool:
        """解除IP封锁"""
        try:
            blocked_key = f"{cls.BLOCKED_IPS_PREFIX}:{ip_address}"
            attempts_key = f"{cls.LOGIN_ATTEMPTS_PREFIX}:{ip_address}"
            
            # 删除封锁和尝试记录
            await redis_client.delete(blocked_key)
            await redis_client.delete(attempts_key)
            
            logger.info(f"IP地址解除封锁: {ip_address}")
            return True
            
        except Exception as e:
            logger.error(f"解除IP封锁失败: {e}")
            return False
    
    @classmethod
    async def get_blocked_ips(cls) -> List[Dict[str, Any]]:
        """获取被封锁的IP列表"""
        try:
            blocked_ips = []
            
            # 扫描所有被封锁的IP
            pattern = f"{cls.BLOCKED_IPS_PREFIX}:*"
            async for key in redis_client.redis.scan_iter(match=pattern):
                ip_address = key.split(":")[-1]
                block_info = await redis_client.get(key)
                
                if block_info:
                    blocked_ips.append({
                        "ip_address": ip_address,
                        "blocked_at": block_info.get("blocked_at"),
                        "reason": block_info.get("reason"),
                        "expires_at": block_info.get("expires_at")
                    })
            
            return blocked_ips
            
        except Exception as e:
            logger.error(f"获取被封锁IP列表失败: {e}")
            return []
    
    @classmethod
    async def get_audit_logs(
        cls,
        start_date: Optional[datetime] = None,
        end_date: Optional[datetime] = None,
        user_id: Optional[int] = None,
        action: Optional[str] = None,
        success: Optional[bool] = None,
        limit: int = 100
    ) -> List[Dict[str, Any]]:
        """获取审计日志"""
        try:
            logs = []
            
            # 设置默认时间范围
            if not start_date:
                start_date = datetime.now() - timedelta(days=7)
            if not end_date:
                end_date = datetime.now()
            
            # 扫描指定日期范围的日志
            current_date = start_date.date()
            while current_date <= end_date.date():
                pattern = f"{cls.AUDIT_LOG_PREFIX}:{current_date.strftime('%Y%m%d')}:*"
                
                async for key in redis_client.redis.scan_iter(match=pattern):
                    log_data = await redis_client.get(key)
                    
                    if log_data:
                        # 应用过滤条件
                        if user_id and log_data.get("user_id") != user_id:
                            continue
                        if action and log_data.get("action") != action:
                            continue
                        if success is not None and log_data.get("success") != success:
                            continue
                        
                        # 检查时间范围
                        log_time = datetime.fromisoformat(log_data.get("timestamp"))
                        if log_time < start_date or log_time > end_date:
                            continue
                        
                        logs.append(log_data)
                        
                        # 限制返回数量
                        if len(logs) >= limit:
                            break
                
                if len(logs) >= limit:
                    break
                
                current_date += timedelta(days=1)
            
            # 按时间倒序排列
            logs.sort(key=lambda x: x.get("timestamp"), reverse=True)
            
            return logs[:limit]
            
        except Exception as e:
            logger.error(f"获取审计日志失败: {e}")
            return []
    
    @classmethod
    async def get_security_statistics(cls) -> Dict[str, Any]:
        """获取安全统计信息"""
        try:
            # 获取最近24小时的统计
            end_time = datetime.now()
            start_time = end_time - timedelta(hours=24)
            
            logs = await cls.get_audit_logs(
                start_date=start_time,
                end_date=end_time,
                limit=1000
            )
            
            # 统计各种事件
            total_events = len(logs)
            login_attempts = len([log for log in logs if log.get("action") == "login"])
            failed_logins = len([log for log in logs if log.get("action") == "login" and not log.get("success")])
            successful_logins = login_attempts - failed_logins
            
            # 统计其他操作
            create_operations = len([log for log in logs if "create" in log.get("action", "")])
            update_operations = len([log for log in logs if "update" in log.get("action", "")])
            delete_operations = len([log for log in logs if "delete" in log.get("action", "")])
            
            # 获取被封锁的IP数量
            blocked_ips = await cls.get_blocked_ips()
            blocked_ip_count = len(blocked_ips)
            
            # 统计活跃IP
            unique_ips = set()
            for log in logs:
                if log.get("ip_address"):
                    unique_ips.add(log.get("ip_address"))
            
            return {
                "time_range": {
                    "start": start_time.isoformat(),
                    "end": end_time.isoformat()
                },
                "event_statistics": {
                    "total_events": total_events,
                    "login_attempts": login_attempts,
                    "successful_logins": successful_logins,
                    "failed_logins": failed_logins,
                    "create_operations": create_operations,
                    "update_operations": update_operations,
                    "delete_operations": delete_operations
                },
                "security_statistics": {
                    "blocked_ips": blocked_ip_count,
                    "unique_ips": len(unique_ips),
                    "login_success_rate": (successful_logins / login_attempts * 100) if login_attempts > 0 else 0
                }
            }
            
        except Exception as e:
            logger.error(f"获取安全统计失败: {e}")
            return {}
    
    @classmethod
    async def cleanup_old_logs(cls, days: int = 30) -> int:
        """清理旧的审计日志"""
        try:
            cutoff_date = datetime.now() - timedelta(days=days)
            deleted_count = 0
            
            # 扫描所有审计日志键
            pattern = f"{cls.AUDIT_LOG_PREFIX}:*"
            async for key in redis_client.redis.scan_iter(match=pattern):
                # 从键名中提取时间戳
                try:
                    timestamp_str = key.split(":")[-1]
                    timestamp = float(timestamp_str)
                    log_date = datetime.fromtimestamp(timestamp)
                    
                    if log_date < cutoff_date:
                        await redis_client.delete(key)
                        deleted_count += 1
                        
                except (ValueError, IndexError):
                    # 无法解析时间戳的键，跳过
                    continue
            
            logger.info(f"清理旧审计日志: {deleted_count} 条")
            return deleted_count
            
        except Exception as e:
            logger.error(f"清理旧审计日志失败: {e}")
            return 0
